#include "FilterWindow.h"

#include <iostream>
#include <highgui.h>
#include <cv.h>

using namespace std;

/* This function had to be global :( */
void globalMouseCallback(int event, int x, int y, int flags, void* win)
{
    FilterWindow* current = ((FilterWindow*)win);
    switch(current->_getMouseBehavior()){
        case FilterWindow::DRAW:
            current->_mouseCallbackDraw(event, x, y, flags);
            break;
        case FilterWindow::LAST_CLICK:
            current->_mouseCallbackLastClick(event, x, y, flags);
            break;
	case FilterWindow::NO_MOUSE:	    
	    break;
    }
}

FilterWindow::FilterWindow(Filter& f, string window_name, MouseBehavior mb) : 
    mb(mb),
    leftDown(false),
    rightDown(false),
    windowName(window_name), 
    filter(f),
    ratio(1)
{
    // This line leaks memory...
    cv::namedWindow(windowName.c_str(), CV_WINDOW_AUTOSIZE);
    cv::Size sz;
    switch(mb){
    case NO_MOUSE:
        break;
    case DRAW:
        sz = f.exportPicture()->size();        
        userMask = cv::Mat(sz, CV_8UC1);
        userMask.setTo(NOTHING);
    case LAST_CLICK:
        lastClick = cv::Point(0,0); 
    default:
        // Register the mouseCallback for both DRAW and LAST_CLICK
        cvSetMouseCallback( windowName.c_str(), globalMouseCallback, this);
    }

}

void FilterWindow::setPixelOnMask(uchar mode, int x, int y)
{
    /* We recieve x,y on the resized image and then we
       convert them to the the real image size */
    cv::Point normPoint(x,y);
    cv::Point realPoint((int)((1/ratio)*x),(int)((1/ratio)*y));
    /* Update the user interaction mask */
    cv::circle( userMask, realPoint, 2, cv::Scalar(mode), 2);
    /* We also draw on the image shown to the user (feedback) */
    switch(mode){
    case cv::GC_BGD:
        cv::circle( latestNormalized, normPoint, 2, RED, 2);
        break;
    case cv::GC_FGD:
        cv::circle( latestNormalized, normPoint, 2, GREEN, 2);
        break;
    }
    /* Update the window */
    imshow(windowName.c_str(), latestNormalized);
}

void FilterWindow::_mouseCallbackDraw(int event, int x, int y, int flags)
{
    /* LEFT_CLICK - Mark as background
       RIGHT_CLICK - Mark as foreground */
    switch( event )
    {
    case CV_EVENT_LBUTTONDOWN: 
        leftDown = true;
        break;
    case CV_EVENT_RBUTTONDOWN: 
        rightDown = true;
        break;
    case CV_EVENT_LBUTTONUP:
        leftDown = false;
        break;
    case CV_EVENT_RBUTTONUP:
        rightDown = false;
        break;
    case CV_EVENT_MOUSEMOVE:
        if(leftDown && rightDown)
            break;
        if(leftDown)
            setPixelOnMask(cv::GC_BGD,  x,  y);
        if(rightDown)
            setPixelOnMask(cv::GC_FGD,  x,  y);
        break;
    }
}

void FilterWindow::_mouseCallbackLastClick(int event, int x, int y, int flags)
{
    /* Only update the last clicked (x,y) */
    switch( event )
    {
    case CV_EVENT_LBUTTONDOWN: 
        lastClick = cv::Point((int)((1/ratio)*x),(int)((1/ratio)*y));
        break;
    }
}

void FilterWindow::refresh() {
    // Reset the user mask
    userMask.setTo(NOTHING);
    if (!filter.refresh(latestDisplayed)) {
        cerr << "Error refreshing";
    } else {
        // Resize the image
        displayNormalized(latestDisplayed);
    }
}

void FilterWindow::highlight(vector<cv::Rect> ROIs) {
    cv::Mat disp;
    latestDisplayed.copyTo(disp);

    for(vector<cv::Rect>::const_iterator r = ROIs.begin(); r != ROIs.end(); r++) {
        cout << "Face!" << endl;
        cv::rectangle(disp, cv::Point((*r).x, (*r).y), 
             cv::Point((*r).x + (*r).width, (*r).y + (*r).height), GREEN, 2);
    }
    displayNormalized(disp);
}

void FilterWindow::displayNormalized(cv::Mat& disp) {
    /* If the image width or height is greater than the defined MAX:
         1) Calculate the height ratio
         2) Calculate the width ratio
         3) Save the min(heightRatio, widthRatio) */
    if(disp.rows > MAX_WIDTH) {
        ratio = (double)(MAX_WIDTH) / disp.rows;
    }
    if(disp.cols > MAX_HEIGHT) {
        ratio = min((double)(MAX_HEIGHT) / disp.cols, ratio);
    }

    // Check if resize is needed, either cases save the last shown image in latestNormalized
    if(ratio != 1) {
        cv::Mat resized;
        cv::Size sz = cv::Size(round(ratio * disp.cols), round(ratio * disp.rows));
        cv::resize(disp, resized,sz);
        latestNormalized = resized;
    }
    else {
        latestNormalized = disp;
    }
    // Update the window
    imshow(windowName.c_str(), latestNormalized);
}

FilterWindow::~FilterWindow() {
    cvDestroyWindow(windowName.c_str());
}

unsigned FilterWindow::loop(unsigned int timeout) {
    //int start_time = now();
    int c=0;
    int i=0;

    while ('\x1b' != (char)c)
    {
        c = cvWaitKey(0);
        switch( (char) c ) {
        case '\x1b':
            cout << "Done with " << windowName << endl;
            break;
        case 'n':
            cout << "Performing iteration... ";
            switch(mb){
            case LAST_CLICK:
                filter.nextIter((void*)&lastClick);
                break;
            case DRAW:
                filter.nextIter((void*)&userMask);
                break;
            case NO_MOUSE:
                filter.nextIter();
                break;
            }

            refresh();
            cout << "Done " << endl;
            break;
        case 'd':
            ostringstream image_name;
            image_name << windowName << "_" << i << ".jpg";

            cout << "Dumping image to disk: " << image_name.str() << "... ";
            cv::Mat result;
            if (!filter.refresh(result)) {
                cerr << "Error refreshing";
            } else {
                cv::imwrite(image_name.str(), result);
                i+=1;
                cout << "Done" << endl;
            }
            
        }
    }
    // return now() - start_time;
    return 0;
}
